package db

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"gitee.com/go-web-2/orm_framework/session/dialect"
	"gitee.com/go-web-2/orm_framework/session/model"
	"gitee.com/go-web-2/orm_framework/session/value"
	_ "github.com/go-sql-driver/mysql"
	"log"
)

type DB struct {
	Db *sql.DB
	Core
}

func (d *DB) IsDB() {
}

func (d *DB) GetCore() Core {
	return d.Core
}

func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
	return d.Db.ExecContext(ctx, query, args...)
}

func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
	return d.Db.QueryContext(ctx, query, args...)
}

func (d *DB) Wait() error {
	err := d.Db.Ping()
	if err != nil && errors.Is(err, sql.ErrConnDone) {
		fmt.Println("数据库尝试连接失败,再次尝试...")
		err = d.Db.Ping()
	}
	return err
}

func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
	tx, err := d.Db.BeginTx(ctx, opts)
	if err != nil {
		return nil, err
	}
	return &Tx{
		Tx: tx,
		Db: d,
	}, nil
}

func (d *DB) BeginTxV2(ctx context.Context, opts *sql.TxOptions) (context.Context, *Tx, error) {
	val := ctx.Value(txKey{})
	if val != nil {
		return ctx, val.(*Tx), nil
	}

	tx, err := d.Db.BeginTx(ctx, opts)
	if err != nil {
		return ctx, nil, err
	}
	ctx = context.WithValue(ctx, txKey{}, tx)
	return ctx, &Tx{
		Tx: tx,
		Db: d,
	}, nil
}

func (d *DB) DoTx(ctx context.Context, opts *sql.TxOptions, fn func(tx *Tx) error) error {
	tx, err := d.BeginTx(ctx, opts)
	if err != nil {
		return err
	}
	panicked := true
	err = fn(tx)
	panicked = false

	defer func() {
		if panicked || err != nil {
			er := tx.Rollback()
			if er != nil {
				log.Println(fmt.Sprintf("业务错误: %s, 回滚错误: %s, 是否panicked: %t\n", err, er, panicked))
				return
			}
			er = tx.Commit()
			if er != nil {
				log.Println("orm: ", er)
				return
			}
		}
	}()
	return err
}

type Config struct {
	Dialect      dialect.Dialect
	ValueCreator value.Creator
}

func WithUnsafeValue() Opt {
	return func(d *DB) {
		d.ValueCreator = value.NewUnsafeValue
	}
}

func WithReflectValue() Opt {
	return func(d *DB) {
		d.ValueCreator = value.NewReflectValue
	}
}

func WithDialect(dialect dialect.Dialect) Opt {
	return func(d *DB) {
		d.Dialect = dialect
	}
}

type Opt func(d *DB)

func OpenDB(sqlDB *sql.DB, opts ...Opt) *DB {
	db := &DB{
		Core: Core{
			R:            &model.Register{},
			Dialect:      dialect.NewMysqlDialect(),
			ValueCreator: value.NewReflectValue,
		},
		Db: sqlDB,
	}

	for _, opt := range opts {
		opt(db)
	}

	return db
}

func NewDB(driverName string, dataSourceName string, opts ...Opt) *DB {
	db, err := sql.Open(driverName, dataSourceName)
	if err != nil {
		panic("orm: " + err.Error())
	}

	return OpenDB(db, opts...)
}

var _ Session = (*DB)(nil)
